<template>
	<view>
		<!-- 控制按钮 -->
		<view style="display: flex;padding-top:10px">
			<view style="width:10px"></view>
			<!-- <view style="flex:1">
				<button type="warn" @click="recReq" style="font-size:16px;padding:0">请求录音权限</button>
			</view> -->
			<view style="width:10px"></view>
			<!-- 最新版本 -->
			<view :class="'zxj-record-layer ' + ( statusText==='说话中...' ? ' zxj-recording zxj-recording-animation' : statusText==='松手取消' ? ' zxj-noRecording' : '')">
				<view 
					:class="'zxj-record-btn'" 
					@touchstart="touchstart"
					@touchmove="touchmove"
					@touchend="touchend">
					<text v-if="statusText === '按住说话'">{{ statusText }} {{ msg }}</text>
				</view>
				<view style="position:absolute;top: -34px;height: 20px;font-size: 12px;color: #999;" v-if="statusText==='说话中...'" class="cancel-tip">松手发送，上移取消</view>
				<view style="position:absolute;top: -34px;height: 20px;font-size: 12px;" v-if="statusText==='松手取消'" class="cancel-tip">松手取消</view>
				<view class="recwave" v-show="statusText !== '按住说话'">
					<canvas type="2d" class="recwave-Histogram3"></canvas>
				</view>
			</view>
			<!-- 最新版本 -->
			<view style="width:10px"></view>
		</view>
		
		<!-- 手撸播放器 -->
		<view style="padding-top:10px;display:none;">
			<TestPlayer ref="player" />
		</view>
	</view>
</template>
	
	
<script> 
	/**这里是逻辑层**/
	/**本例子是VUE的选项式 API (Options API)风格，vue2 vue3均支持，如果你使用的vue3 setup组合式 API (Composition API) 编写时，请在import后面取到当前实例this，在需要this的地方传vue3This变量即可，具体调用可以参考 page_vue3____composition_api.vue
	import { getCurrentInstance } from 'vue';
	var vue3This=getCurrentInstance().proxy; //必须定义到最外面，放import后面即可 */
	import TestPlayer from './test_player___.vue'; //手撸的一个跨平台播放器
	/** 先引入Recorder （ 需先 npm install recorder-core ）**/
	import Recorder from 'recorder-core'; //注意如果未引用Recorder变量，可能编译时会被优化删除（如vue3 tree-shaking），请改成 import 'recorder-core'，或随便调用一下 Recorder.a=1 保证强引用
	/** H5、小程序环境中：引入需要的格式编码器、可视化插件，App环境中在renderjs中引入 **/
	// 注意：如果App中需要在逻辑层中调用Recorder的编码/转码功能，需要去掉此条件编译，否则会报未加载编码器的错误
	// #ifdef H5 || MP-WEIXIN
		//按需引入需要的录音格式编码器，用不到的不需要引入，减少程序体积；H5、renderjs中可以把编码器放到static文件夹里面用动态创建script来引入，免得这些文件太大
		import 'recorder-core/src/engine/mp3.js'
		import 'recorder-core/src/engine/mp3-engine.js'
		import 'recorder-core/src/engine/wav.js'
		import 'recorder-core/src/engine/pcm.js'
		import 'recorder-core/src/engine/g711x'
	
		//可选引入可视化插件
		import 'recorder-core/src/extensions/waveview.js'
		import 'recorder-core/src/extensions/wavesurfer.view.js'
	
		import 'recorder-core/src/extensions/frequency.histogram.view.js'
		import 'recorder-core/src/extensions/lib.fft.js'
		
		//实时播放语音，仅支持h5
		import 'recorder-core/src/extensions/buffer_stream.player.js'
		//测试用根据简谱生成一段音乐
		import 'recorder-core/src/extensions/create-audio.nmn2pcm.js'
	// #endif
	
	/** 引入RecordApp **/
	import RecordApp from 'recorder-core/src/app-support/app.js'
	//【所有平台必须引入】uni-app支持文件
	import '../../uni_modules/Recorder-UniCore/app-uni-support.js'
	
	var disableOgg = false;
	// #ifdef MP-WEIXIN
		//可选引入微信小程序支持文件
		import 'recorder-core/src/app-support/app-miniProgram-wx-support.js'
		disableOgg = true; //小程序不测试ogg js文件太大
	// #endif
	
	
	// #ifdef H5 || MP-WEIXIN
		//H5、renderjs中可以把编码器放到static文件夹里面用动态创建script来引入，免得这些文件太大
		import 'recorder-core/src/engine/beta-amr'
		import 'recorder-core/src/engine/beta-amr-engine'
	// #endif
	// #ifdef H5
		//app、h5测试ogg，小程序不测试ogg js文件太大
		import 'recorder-core/src/engine/beta-ogg'
		import 'recorder-core/src/engine/beta-ogg-engine'
	// #endif
	/** 可选：App中引入原生录音插件来进行录音，兼容性和体验更好，原生插件市场地址: https://ext.dcloud.net.cn/plugin?name=Recorder-NativePlugin （试用无任何限制）
		在调用RecordApp.RequestPermission之前进行配置，建议放到import后面直接配置（全局生效）
		也可以判断一下只在iOS上或Android上启用，不判断就都启用，比如判断iOS：RecordApp.UniIsApp()==2 */
	RecordApp.UniNativeUtsPlugin={nativePlugin:true}; //目前仅支持原生插件，uts插件不可用
	//App中提升后台录音的稳定性：配置了原生插件后，可配置 `RecordApp.UniWithoutAppRenderjs=true` 禁用renderjs层音频编码（WebWorker加速），变成逻辑层中直接编码（但会降低逻辑层性能），后台运行时可避免部分手机WebView运行受限的影响
	//App中提升后台录音的稳定性：需要启用后台录音保活服务（iOS不需要），Android 9开始，锁屏或进入后台一段时间后App可能会被禁止访问麦克风导致录音静音、无法录音（renderjs中H5录音也受影响），请调用配套原生插件的`androidNotifyService`接口，或使用第三方保活插件
	
	export default {
		components: { TestPlayer },
		data() {
			return {
				startY: 0,
				isRecording: false,
				isShowingCancelTip: false,
				statusText: '按住说话',
				tempFilePath: '',
				msg: '2',


				recType:"mp3",
				recSampleRate:16000,
				recBitRate:16,
				takeoffEncodeChunkSet:false,
				takeoffEncodeChunkMsg:"",
				useAEC:false,
				useANotifySrv:true,
				appUseH5Rec:false,
				showUpload:false,
				recwaveChoiceKey:"Histogram3",
				pageDeep:0,pageNewPath:"main_recTest",
				recpowerx:0,
				disableOgg:disableOgg,
				evalExecCode:"",
				recStart_setSpeaker:false,
				recStart_speakerOff:false,
				recStart_speakerHds:true,
				testNP_PcmPlayerShow:false,
				testMsgs:[],
				reclogs:[],
				reclogLast:""
			}
		},
		mounted() {
			var vueVer=[];
			var vv=typeof(Vue)!="undefined" && Vue && Vue.version; if(vv) vueVer.push("Vue.version:"+vv);
			var v3=(((this.$||{}).appContext||{}).app||{}).version; if(v3) vueVer.push("appContext.app.version:"+v3);
			var v2=(((this.$root||{}).constructor||{}).super||{}).version; if(v2) vueVer.push("constructor.super:"+v2);
			this.reclog("页面mounted("+getCurrentPages().length+"层)"
				+"，Vue="+vueVer.join("/")
				+"，WebViewId="+(this.$root.$page&&this.$root.$page.id||"?")
				+"，ComponentId=_$id:"+(this._$id||"?")+"/"+"$.uid:"+(this.$&&this.$.uid||"?")
				+"，Recorder.LM="+Recorder.LM
				+"，RecordApp.LM="+RecordApp.LM
				+"，UniSupportLM="+RecordApp.UniSupportLM
				+"，UniJsSource="+RecordApp.UniJsSource.IsSource);
			this.pageDeep=getCurrentPages().length;
			this.pageNewPath=/main_recTest/.test(this.getRouteStr())?"page_index2":"main_recTest";
			
			this.isMounted=true;
			this.uniPage__onShow(); //onShow可能比mounted先执行，页面准备好了时再执行一次
			
			//可选，立即显示出环境信息
			this.reclog("正在执行Install，请勿操作...","#f60");
			RecordApp.Install(()=>{
				this.reclog("Install成功，环境："+this.currentKeyTag(),2);
				this.reclog("请先请求录音权限，然后再开始录音");
			},(err)=>{
				this.reclog("RecordApp.Install出错："+err,1);
			});
		},
		/*#ifdef VUE3*/unmounted()/*#endif*/ /*#ifndef VUE3*/destroyed()/*#endif*/ {
			RecordApp.Stop(); //清理资源，如果打开了录音没有关闭，这里将会进行关闭
		},
		onShow() { //当组件用时没这个回调
			if(this.isMounted) this.uniPage__onShow(); //onShow可能比mounted先执行，页面可能还未准备好
			this.recReq()
		},
		methods:{
			touchstart(e) {
				this.startY = e.touches[0].clientY;
				this.isRecording = true;
				this.statusText = '说话中...';
				const options = {
					duration: 60000,
					sampleRate: 16000,
					numberOfChannels: 1,
					encodeBitRate: 96000,
					format: 'mp3'
				};
				this.recStart()
			},
			touchmove(e) {
				const currentY = e.touches[0].clientY;
				const moveDistance = currentY - this.startY;
				if (this.isRecording) {
					if (moveDistance < -50) {
						this.isShowingCancelTip = true;
						this.statusText = '松手取消';
						// 暂停
						this.recPause()
					} else {
						this.isShowingCancelTip = false;
						this.statusText = '说话中...';
						// 继续
						this.recResume()
					}
				}
			},
			touchend() {
				console.log('this.statusText====>',this.statusText)
				console.log('this.isShowingCancelTip====>',this.isShowingCancelTip)
					if (this.isShowingCancelTip) {
						uni.showToast({
							title: '语音已取消发送',
							icon: 'none'
						});
					} else {
						this.recStop()
						uni.showToast({
							title: '录音成功，已发送',
							icon: 'success'
						});
					}
					this.isRecording = false;
					this.isShowingCancelTip = false;
					this.statusText = '按住说话';
			},


			
			uniPage__onShow(){ //页面onShow时【必须调用】的函数，传入当前组件this
				RecordApp.UniPageOnShow(this);
			},
			currentKeyTag(){
				if(!RecordApp.Current) return "[?]";
				// #ifdef APP
				var tag2="Renderjs+H5";
				if(RecordApp.UniNativeUtsPlugin){
					tag2=RecordApp.UniNativeUtsPlugin.nativePlugin?"NativePlugin":"UtsPlugin";
				}
				return RecordApp.Current.Key+"("+tag2+")";
				// #endif
				return RecordApp.Current.Key;
			},
			recReq(){
				if(this.appUseH5Rec){//测试时指定使用h5录音
					RecordApp.UniNativeUtsPlugin=null;
				}else{
					RecordApp.UniNativeUtsPlugin={nativePlugin:true}; //恢复原生插件配置值
					RecordApp.UniCheckNativeUtsPluginConfig(); //可以检查一下原生插件配置是否有效
					RecordApp.UniNativeUtsPlugin_JsCall=(data)=>{ //可以绑定原生插件的jsCall回调
						if(data.action=="onLog"){ //显示原生插件日志信息
							this.reclog("[Native.onLog]["+data.tag+"]"+data.message, data.isError?1:"#bbb", {noLog:1});
						}
					};
				}
				RecordApp.UniWebViewActivate(this); //App环境下必须先切换成当前页面WebView
				RecordApp.RequestPermission(()=>{
					if(this.reqOkCall)this.reqOkCall(); this.reqOkCall=null; //留别的组件内调用的回调
				},(msg,isUserNotAllow)=>{
					if(isUserNotAllow){//用户拒绝了录音权限
						//这里你应当编写代码进行引导用户给录音权限，不同平台分别进行编写
					}
				});
			},
			recStart(){
				this.$refs.player.setPlayBytes(null);
				this.takeoffEncodeChunkMsg="";var takeEcCount=0,takeEcSize=0;
				this.takeEcChunks=this.takeoffEncodeChunkSet?[]:null;
				this.watchDogTimer=0; this.wdtPauseT=0; var processTime=0;
				
				if(this.useANotifySrv) this.tryStart_androidNotifyService(); //Android App+原生插件环境下，如需后台或锁屏录音，就必须启用后台录音保活服务（iOS不需要），Android 9开始，锁屏或进入后台一段时间后App可能会被禁止访问麦克风导致录音静音、无法录音（renderjs中H5录音、原生插件录音均受影响），因此需要调用原生插件的`androidNotifyService`接口保活，或使用第三方保活插件
				
				this.reclog(this.currentKeyTag()+" 正在打开...");
				RecordApp.UniWebViewActivate(this); //App环境下必须先切换成当前页面WebView
				RecordApp.Start({
					type:this.recType
					,sampleRate:this.recSampleRate
					,bitRate:this.recBitRate
					,audioTrackSet:!this.useAEC?null:{ //配置回声消除，H5、App、小程序均可用，但并不一定会生效；注意：H5、App+renderjs中需要在请求录音权限前进行相同配置RecordApp.RequestPermission_H5OpenSet后此配置才会生效
						noiseSuppression:true,echoCancellation:true,autoGainControl:true
					}
					
					,setSpeakerOff:!this.recStart_setSpeaker? null : { //使用原生录音插件时，可以提供一个扬声器外放和听筒播放的切换默认配置
						off:this.recStart_speakerOff, headset:this.recStart_speakerHds
					}
					
					,onProcess:(buffers,powerLevel,duration,sampleRate,newBufferIdx,asyncEnd)=>{
						//全平台通用：可实时上传（发送）数据，配合Recorder.SampleData方法，将buffers中的新数据连续的转换成pcm上传，或使用mock方法将新数据连续的转码成其他格式上传，可以参考Recorder文档里面的：Demo片段列表 -> 实时转码并上传-通用版；基于本功能可以做到：实时转发数据、实时保存数据、实时语音识别（ASR）等
						
						//注意：App里面是在renderjs中进行实际的音频格式编码操作，此处的buffers数据是renderjs实时转发过来的，修改此处的buffers数据不会改变renderjs中buffers，所以不会改变生成的音频文件，可在onProcess_renderjs中进行修改操作就没有此问题了；如需清理buffers内存，此处和onProcess_renderjs中均需要进行清理，H5、小程序中无此限制
						//注意：如果你要用只支持在浏览器中使用的Recorder扩展插件，App里面请在renderjs中引入此扩展插件，然后在onProcess_renderjs中调用这个插件；H5可直接在这里进行调用，小程序不支持这类插件；如果调用插件的逻辑比较复杂，建议封装成js文件，这样逻辑层、renderjs中直接import，不需要重复编写
						
						this.recpowerx=powerLevel;
						this.recpowert=this.formatTime(duration,1)+" / "+powerLevel;
						processTime=Date.now();
						
						//H5、小程序等可视化图形绘制，直接运行在逻辑层；App里面需要在onProcess_renderjs中进行这些操作
						// #ifdef H5 || MP-WEIXIN
						var wave=this.waveStore&&this.waveStore[this.recwaveChoiceKey];
						if(wave){
							wave.input(buffers[buffers.length-1],powerLevel,sampleRate);
						}
						// #endif
						//实时语音通话对讲，实时处理录音数据
						if(this.wsVoiceProcess) this.wsVoiceProcess(buffers,powerLevel,duration,sampleRate,newBufferIdx);
						
						//实时释放清理内存，用于支持长时间录音；在指定了有效的type时，编码器内部可能还会有其他缓冲，必须同时提供takeoffEncodeChunk才能清理内存，否则type需要提供unknown格式来阻止编码器内部缓冲，App的onProcess_renderjs中需要进行相同操作
						if(this.takeEcChunks){
							if(this.clearBufferIdx>newBufferIdx){ this.clearBufferIdx=0 } //重新录音了就重置
							for(var i=this.clearBufferIdx||0;i<newBufferIdx;i++) buffers[i]=null;
							this.clearBufferIdx=newBufferIdx;
						}
					}
					,onProcess_renderjs:`function(buffers,powerLevel,duration,sampleRate,newBufferIdx,asyncEnd){
						//App中在这里修改buffers才会改变生成的音频文件
						//App中是在renderjs中进行的可视化图形绘制，因此需要写在这里，this是renderjs模块的this（也可以用This变量）；如果代码比较复杂，请直接在renderjs的methods里面放个方法xxxFunc，这里直接使用this.xxxFunc(args)进行调用
						var wave=this.waveStore&&this.waveStore[this.recwaveChoiceKey];
						if(wave) wave.input(buffers[buffers.length-1],powerLevel,sampleRate);
						
						//和onProcess中一样进行释放清理内存，用于支持长时间录音
						if(${this.takeEcChunks?1:0}){
							if(this.clearBufferIdx>newBufferIdx){ this.clearBufferIdx=0 } //重新录音了就重置
							for(var i=this.clearBufferIdx||0;i<newBufferIdx;i++) buffers[i]=null;
							this.clearBufferIdx=newBufferIdx;
						}
					}`
					
					,takeoffEncodeChunk:!this.takeoffEncodeChunkSet?null:(chunkBytes)=>{
						//全平台通用：实时接收到编码器编码出来的音频片段数据，chunkBytes是Uint8Array二进制数据，可以实时上传（发送）出去
						//App中如果未配置RecordApp.UniWithoutAppRenderjs时，建议提供此回调，因为录音结束后会将整个录音文件从renderjs传回逻辑层，由于uni-app的逻辑层和renderjs层数据交互性能实在太拉跨了，大点的文件传输会比较慢，提供此回调后可避免Stop时产生超大数据回传
						takeEcCount++; takeEcSize+=chunkBytes.byteLength;
						this.takeoffEncodeChunkMsg="已接收到"+takeEcCount+"块，共"+takeEcSize+"字节";
						this.takeEcChunks.push(chunkBytes);
						
						//App中使用原生插件时，可方便的将数据实时保存到同一文件，第一帧时append:false新建文件，后面的append:true追加到文件
						//RecordApp.UniNativeUtsPluginCallAsync("writeFile",{path:"xxx.mp3",append:回调次数!=1, dataBase64:RecordApp.UniBtoa(chunkBytes.buffer)}).then(...).catch(...)
					}
					,takeoffEncodeChunk_renderjs:!this.takeoffEncodeChunkSet?null:`function(chunkBytes){
						//App中这里可以做一些仅在renderjs中才生效的事情，不提供也行，this是renderjs模块的this（也可以用This变量）
					}`
					
					,start_renderjs:`function(){
						//App中可以放一个函数，在Start成功时renderjs中会先调用这里的代码，this是renderjs模块的this（也可以用This变量）
						//放一些仅在renderjs中才生效的事情，比如初始化，不提供也行
					}`
					,stop_renderjs:`function(aBuf,duration,mime){
						//App中可以放一个函数，在Stop成功时renderjs中会先调用这里的代码，this是renderjs模块的this（也可以用This变量）
						this.audioData=aBuf; //留着给Stop时进行转码成wav播放
					}`
				},()=>{
					this.reclog(this.currentKeyTag()+" 录制中："+this.recType
						+" "+this.recSampleRate+" "+this.recBitRate+"kbps"
						+(this.takeoffEncodeChunkSet?" takeoffEncodeChunk":"")
						+(this.useAEC?" useAEC":"")
						+(this.appUseH5Rec?" appUseH5Rec":""),2);
					//创建音频可视化图形绘制
					this.initWaveStore();
					
					//【稳如老狗WDT】可选的，监控是否在正常录音有onProcess回调，如果长时间没有回调就代表录音不正常
					if(RecordApp.Current.CanProcess()){
						var wdt=this.watchDogTimer=setInterval(()=>{
							if(wdt!=this.watchDogTimer){ clearInterval(wdt); return } //sync
							if(Date.now()<this.wdtPauseT) return; //如果暂停录音了就不检测：puase时赋值this.wdtPauseT=Date.now()*2（永不监控），resume时赋值this.wdtPauseT=Date.now()+1000（1秒后再监控）
							if(Date.now()-(processTime||startTime)>1500){ clearInterval(wdt);
								this.reclog(processTime?"录音被中断":"录音未能正常开始",1);
								// ... 错误处理，关闭录音，提醒用户
							}
						},1000);
					}else{
						this.reclog("当前环境不支持onProcess回调，不启用watchDogTimer","#aaa"); //目前都支持回调
					}
					var startTime=Date.now();
				},(msg)=>{
					this.reclog(this.currentKeyTag()+" 开始录音失败："+msg,1);
				});
			},
			recPause(){
				if(RecordApp.GetCurrentRecOrNull()){
					RecordApp.Pause();
					this.wdtPauseT=Date.now()*2; //永不监控onProcess超时
					this.reclog("已暂停");
				}
			},
			recResume(){
				if(RecordApp.GetCurrentRecOrNull()){
					RecordApp.Resume();
					this.wdtPauseT=Date.now()+1000; //1秒后再监控onProcess超时
					this.reclog("继续录音中...");
				}
			},
			recStopX(){
				this.tryClose_androidNotifyService(); //关闭后台录音保活服务
				this.watchDogTimer=0; //停止监控onProcess超时
				RecordApp.Stop(
					null //success传null就只会清理资源，不会进行转码
					,(msg)=>{
						this.reclog("已清理，错误信息："+msg);
					}
				);
			},
			arrayBufferToBase64 (arrayBuffer) {
				// 创建一个 Uint8Array 视图来读取 ArrayBuffer
				const uint8Array = new Uint8Array(arrayBuffer)

				// 将 Uint8Array 转换为二进制字符串
				let binaryString = ''
				for (let i = 0; i < uint8Array.length; i++) {
					binaryString += String.fromCharCode(uint8Array[i])
				}

				// 使用 btoa() 函数将二进制字符串转换为 Base64 编码
				return btoa(binaryString)
			},
			recStop(){
				const _this = this;
				this.reclog("正在结束录音...");
				this.tryClose_androidNotifyService(); //关闭后台录音保活服务
				this.watchDogTimer=0; //停止监控onProcess超时
				
				RecordApp.Stop((aBuf,duration,mime)=>{
					//全平台通用：aBuf是ArrayBuffer音频文件二进制数据，可以保存成文件或者发送给服务器
					//App中如果在Start参数中提供了stop_renderjs，renderjs中的函数会比这个函数先执行
					const base64String = this.arrayBufferToBase64(aBuf)
					uni.request({
						url: '/yuyin-admin/yuyin/getText', // 替换为实际的 API 地址
						method: 'post',
						data: {
							file: base64String,
						},
						header: {
							'Content-Type': 'application/x-www-form-urlencoded'
						},
						success: function (res) {
							const _msg = _this.msg
							// _this.msg += res.data.text
							_this.msg = res.data.text
							_this.sendClick()
							setTimeout(() => {
								_this.msg = _msg
							}, 500);
							uni.hideLoading();
						},
						fail: function (err) {
							console.error('请求失败:', err)
							this.msg = 'ddd'
							
							uni.hideLoading();
						},
						complete: function () {
							this.msg = 'ddd'
							console.log('请求完成')
							uni.hideLoading();
						}
					})

					var recSet=(RecordApp.GetCurrentRecOrNull()||{set:{type:this.recType}}).set;
					var testPlay_aBuf_renderjs="this.audioData";
					
					//如果使用了takeoffEncodeChunk，Stop的aBuf长度是0，数据早已存到了takeEcChunks数组里面，直接合并成完整音频文件
					if(this.takeEcChunks){
						testPlay_aBuf_renderjs=""; //renderjs的数据是空的
						this.reclog("启用takeoffEncodeChunk后Stop返回的blob长度为0不提供音频数据");
						var len=0; for(var i=0;i<this.takeEcChunks.length;i++)len+=this.takeEcChunks[i].length;
						var chunkData=new Uint8Array(len);
						for(var i=0,idx=0;i<this.takeEcChunks.length;i++){
							var itm=this.takeEcChunks[i]; chunkData.set(itm,idx); idx+=itm.length;
						}
						aBuf=chunkData.buffer;
						this.reclog("takeoffEncodeChunk接收到的音频片段，已合并成一个音频文件 "+aBuf.byteLength+"字节");
					}
					
					//【测试】用变量保存起来，别的地方调用
					this.lastRecType=recSet.type;
					this.lastRecBuffer=aBuf;
					
					//【测试】播放，部分格式会转码成wav播放
					this.$refs.player.setPlayBytes(aBuf,testPlay_aBuf_renderjs,duration,mime,recSet,Recorder);
				},(msg)=>{
					this.reclog("结束录音失败："+msg,1);
				});
			},
			
			
			//Android App启用后台录音保活服务，需要原生插件支持，注意必须RecordApp.RequestPermission得到权限后调用
			tryStart_androidNotifyService(){
				if(RecordApp.UniIsApp() && !this._tips_anfs){ this._tips_anfs=1;
					this.reclog("App中提升后台录音的稳定性：需要启用后台录音保活服务（iOS不需要），Android 9开始，锁屏或进入后台一段时间后App可能会被禁止访问麦克风导致录音静音、无法录音（App中H5录音也受影响），需要原生层提供搭配常驻通知的Android后台录音保活服务（Foreground services）；可调用配套原生插件的androidNotifyService接口，或使用第三方保活插件","#4face6");
				}
				if(RecordApp.UniIsApp()!=1) return; //非Android App不处理
				if(!RecordApp.UniNativeUtsPlugin) return; //未使用原生插件
				
				this._NotifyService=false;
				RecordApp.UniNativeUtsPluginCallAsync("androidNotifyService",{
					title:"正在录音"
					,content:"正在录音中，请勿关闭App运行"
				}).then((data)=>{
					this._NotifyService=true;
					var nCode=data.notifyPermissionCode, nMsg=data.notifyPermissionMsg;
					this.reclog("搭配常驻通知的Android后台录音保活服务已打开，ForegroundService已运行(通知可能不显示或会延迟显示，并不影响服务运行)，通知显示状态(1有通知权限 3可能无权限)code="+nCode+" msg="+nMsg,2);
				}).catch((e)=>{
					this.reclog("原生插件的androidNotifyService接口调用出错："+e.message,1);
					this.reclog("如果你已集成了配套的原生录音插件，并且是打包自定义基座运行，请检查本项目根目录的AndroidManifest.xml里面是否已经解开了注释，否则被注释掉的service不会包含在App中",1);
				});
			},
			tryClose_androidNotifyService(){
				if(!this._NotifyService) return;   this._NotifyService=false;
				RecordApp.UniNativeUtsPluginCallAsync("androidNotifyService",{
					close:true
				}).then(()=>{
					this.reclog("已关闭搭配常驻通知的Android后台录音保活服务");
				}).catch((e)=>{
					this.reclog("原生插件的androidNotifyService接口调用出错："+e.message,1);
				});
			},
			
			
			clearLogs(){ 
				this.testMsgs=[];
				this.reclogs=[]; 
			},
			addTestMsg(msg,color){
				var now=new Date();
				var t=("0"+now.getHours()).substr(-2)
					+":"+("0"+now.getMinutes()).substr(-2)
					+":"+("0"+now.getSeconds()).substr(-2);
				var txt="["+t+"]"+msg;
				this.testMsgs.splice(0,0,{msg:txt,color:color});
				this.reclogLast={txt:txt,color:color};
			},
			reclog(msg,color,set){
				var now=new Date();
				var t=("0"+now.getHours()).substr(-2)
					+":"+("0"+now.getMinutes()).substr(-2)
					+":"+("0"+now.getSeconds()).substr(-2);
				var txt="["+t+"]"+msg;
				if(!set||!set.noLog)console.log(txt);
				this.reclogs.splice(0,0,{txt:txt,color:color});
				this.reclogLast={txt:txt,color:color};
			},
			recTypeClick(e){
				var type=e.target.dataset.type;
				if(type){
					this.recType=type;
				}
			},
			appUseH5RecClick(){
				this.appUseH5Rec=!this.appUseH5Rec;
				RecordApp.Current=null;
				this.reclog('切换了appUseH5Rec='+this.appUseH5Rec+'，重新请求录音权限后生效',"#f60");
			},
			
			// 可视化波形，这里是一次性创建多个波形，可以参考page_i18n.vue只创建一个波形会简单一点
			initWaveStore(){
				//if(isWx && this.waveStore)return;//可以限制初始化一次；如果你的canvas所在的view存在v-if之类的会重新创建了对应的view，必须将波形重新进行初始化才能使用；如果使用的是同一个view，重新初始化后如果上次的动画没有完成时，小程序中开头部分新的波形会和老的波形相互干扰，老动画完成后恢复正常，App、H5无此问题
				var store=this.waveStore=this.waveStore||{};//这个测试demo会创建多个可视化，所以用个对象存起来，只有一个的时候请直接用个变量存一下即可 不用搞这么复杂
				var webStore=`var store=this.waveStore=this.waveStore||{};`;//在renderjs中执行，this是renderjs模块的this
				webStore+=`this.recwaveChoiceKey="${this.recwaveChoiceKey}";`;//把当前选中的波形也传过去
				RecordApp.UniFindCanvas(this,[".recwave-Histogram3"],`${webStore}
					store.Histogram3=Recorder.FrequencyHistogramView({compatibleCanvas:canvas1, width:100%, height:300
						,lineCount:20,position:0,minHeight:10,fallDuration:400,stripeEnable:false,mirrorEnable:true
						,linear:[0,"rgb(43, 102, 246)",1,"rgb(43, 102, 246)"]});
				`,(canvas1)=>{
					store.Histogram3=Recorder.FrequencyHistogramView({compatibleCanvas:canvas1, width:300, height:300
						,lineCount:20,position:0,minHeight:10,fallDuration:400,stripeEnable:false,mirrorEnable:true
						,linear:[0,"rgb(43, 102, 246)",1,"rgb(43, 102, 246)"]});
				});
			},
			formatTime(ms,showSS){
				var ss=ms%1000;ms=(ms-ss)/1000;
				var s=ms%60;ms=(ms-s)/60;
				var m=ms%60;ms=(ms-m)/60;
				var h=ms, v="";
				if(h>0) v+=(h<10?"0":"")+h+":";
				v+=(m<10?"0":"")+m+":";
				v+=(s<10?"0":"")+s;
				if(showSS)v+="″"+("00"+ss).substr(-3);;
				return v;
			},
			getRouteStr(){
				var url=this.$page&&this.$page.route||this.$root.route; //vue2 || vue3
				if(!url && this.$root.$scope){//wx
					url=this.$root.$scope.route;
				}
				return "/"+url;
			},
			reloadPage(){
				var url=this.getRouteStr();
				console.log("刷新页面 url="+url);
				if(getCurrentPages().length==1){
					uni.reLaunch({ url:url })
				}else{
					uni.navigateBack({animationDuration:0,success:()=>{ setTimeout(()=>{
						uni.navigateTo({url:url})
					},300); }});
				}
			}
		}
	}
	</script>

	<style>
	.recwaveChoice{
		cursor: pointer;
		display:inline-block;
		vertical-align: bottom;
		border-right:1px solid #ccc;
		background:#ddd;
		line-height:28px;
		font-size:12px;
		color:#666;
		padding:0 5px;
	}
	.recwaveChoice:first-child{
		border-radius: 99px 0 0 99px;
	}
	.recwaveChoice:last-child{
		border-radius: 0 99px 99px 0;
		border-right:none;
	}
	.recwaveChoice.slc,.recwaveChoice:hover{
		background:#f60;
		color:#fff;
	}
	.recwave{
		height:50px;
		width:100%;
		position: absolute;
		bottom: 0;
		z-index: 1;
	}
	.recwave canvas{
		margin: 0 auto;
		height:50px;
		width:300px;
	}
	</style>
	<style lang="less">
	.zxj-record-layer {
		width: calc( 100vw - 40px);
		box-sizing: border-box;
		height: 50px;
		position: fixed;
		z-index: 10;
		bottom: 20px;
		left: 20px;
		display: flex;
		flex-direction: column;
		align-items: center;
		background: #fff;
		// border: 1px solid red;
		box-shadow: 0px 0px 6px 0px rgba(0,0,0,0.15);
		border-radius: 12px;
		font-weight: 500;
		letter-spacing: 2px;
	}

	.zxj-record-btn {
		font-size: 16px;
		line-height: 50px;
		width: 100%;
		height: 50px;
		text-align: center;
		color: #000;
		position: absolute;
		left: 0;
		top: 0;
		z-index: 100;
	}

	.cancel-tip {
		color: red;
		margin-top: 10px;
	}
	.zxj-recording{
		color: #fff;
		background: rgba(43, 102, 246, 0.1);
		border: 1px solid rgb(43, 102, 246);
		box-shadow: 0px 0px 6px 0px rgba(43, 102, 246,0.15);
	}
	.zxj-noRecording{
		color: #fff;
		background:  rgba(234, 51, 35, 0.1);
		box-shadow: 0px 0px 6px 0px rgba(234, 51, 35,0.15);
		border: 1px solid rgb(234, 51, 35);
	}
	.zxj-recording-animation {
		// animation: recordingFlash 1s infinite;
	}
	@keyframes recordingFlash {
		0% {
			opacity: 1;
		}
		50% {
			opacity: 0.5;
		}
		100% {
			opacity: 1;
		}
	}
</style>